/*
 
 A tester for ex3 OS - Spring 2011.
 
*/


// includes:
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <sys/time.h>
#include <sys/resource.h>
#include <string>
#include <sstream>
#include <outputdevice.h>


// globals:
char * OUT1 = (char *) "out1.txt";
char * OUT2 = (char *) "out2.txt";
int NUMOPS = 0 ;
bool FLUSH_FLAG = false ;
bool CLOSE_FLAG = false ;
bool ILLEGAL_FLUSH = false ;
bool BIG_BUFFER = false ;


// tests:
int test1() ;
int test2() ;
int test3 (bool init , bool preClose) ;
int test5() ;
int test6() ;
int test7() ;
int test8() ;
int test9 (bool big_buf) ;
int test10() ;
int test11() ;
int test12() ;
int test13() ;

// main:
int main (int argc , char ** argv) {
	int ret_val = 0;
	if (argc != 2) {
		printf ("[ERROR] Usage: %s <test_num>\n" , argv[0]) ;
		exit (-1) ;
	}
	
	int test_num = atoi (argv[1]) ;
	if (test_num == 0) {
		printf ("[ERROR] %s called with non-numeric test number.\n" ,
				argv[0]) ;
		exit (-1) ;
	}
	
	// Set random seed:
	srand ((unsigned)time(NULL)) ;
	
	printf ("[INFO] Running test %d ...\n" , test_num) ;
	fflush (stdout) ;
	
	switch (test_num) {
		case 1:
			ret_val = test1(); break;
		case 2:
			ret_val = test2(); break;
		case 3:
			ret_val = test3 (true , false); break;  // with initialization
		case 5:
			ret_val = test5(); break;
		case 6:
			ret_val = test6(); break;
		case 7:
			ret_val = test7(); break;
		case 8:
			ret_val = test8(); break;
		case 9:
			ret_val = test9 (false); break; // no big buffers
		case 10:
			ret_val = test10(); break;
		case 11:
			ret_val = test11(); break;
		case 12:
			ret_val = test12(); break;
		case 13:
			ret_val = test13(); break;
		default:
			printf ("[ERROR] Test %d is not handled.\n" , test_num) ;
			ret_val = -1;
	} // switch (test_num)
	
	//pthread_exit(NULL);

	usleep(0x1fffff);

	return ret_val ;
}


// functions:

void * mytimewriter (void * thread_id) {

	std::stringstream stream, stream2, stream3 ;
	timeval a;
	unsigned long long int diff, useconds_before,useconds_after,useconds_after_flush;

	gettimeofday(&a, NULL);
	useconds_before = a.tv_sec*1000 + a.tv_usec;

	stream << "Thread " << (long) thread_id;
	stream << "\tbefore\t" << useconds_before;
	stream << std::endl ;

	int len = stream.str().size() ;
        char * buf = (char *) malloc (len+1) ;
	strcpy (buf , stream.str().c_str()) ;
	int id = write2device(buf, len);
	free(buf);
	
	gettimeofday(&a, NULL);
	useconds_after = a.tv_sec*1000 + a.tv_usec;
	diff = useconds_after < useconds_before ? 0 : useconds_after-useconds_before;
	
	stream2 << "Thread " << (long) thread_id;
	stream2 << "\twrite\t" << diff;
	stream2 << std::endl ;
	
	len = stream2.str().size() ;
        buf = (char *) malloc (len+1) ;
        strcpy (buf , stream2.str().c_str()) ;
        id = write2device(buf, len);
        free(buf);

	if (FLUSH_FLAG) {
		flush2device (id);
	}
	
	gettimeofday(&a, NULL);
        useconds_after_flush = a.tv_sec*1000 + a.tv_usec;
	diff = useconds_after_flush < useconds_before ? 0 : useconds_after_flush-useconds_before;
        
        stream3 << "Thread " << (long) thread_id;
        stream3 << "\tflush\t" << diff;
        stream3 << std::endl ;


	len = stream3.str().size() ;
	buf = (char *) malloc (len+1) ;
	strcpy (buf , stream3.str().c_str()) ;
	id = write2device(buf, len);
	free(buf);

	return NULL;
}

void * mywriter (void * thread_id) {
	
	sleep(1);
	
	for (int i = 0; i < NUMOPS; ++i) {
		
		// printf ("[DEBUG] CP mywriter: %d (%d)\n" , i+1 , NUMOPS) ;
		
		const std::string once ("writer") ;
		int times = int (fabs(((double)rand()/(double)RAND_MAX) * 50.0)+1) ;
		std::stringstream stream ;
		stream << "Thread " << (long) thread_id;
		stream << " (" << i+1 << "): " ;
		for (int j = 0 ; j < times ; ++j) {
			stream << once << j << " " ;
		}
		if (BIG_BUFFER) {
			static const int extra = 10000 ;
			for (int k = 0 ; k < extra ; ++k) {
				stream << "_BIG_" ;
			}
		}
		stream << std::endl ;
		
		int len = stream.str().size() ;
		char * buf = (char *) malloc (len+1) ;
		strcpy (buf , stream.str().c_str()) ;
		int id = write2device(buf, len);
		free(buf);
		
		if (FLUSH_FLAG) {
			if (ILLEGAL_FLUSH) {
				id++ ;
			}
			flush2device (id) ;
		}
	}
	
	return NULL;
}


void * myflusher (void * thread_id) {
	
	sleep(1);
	
	if (CLOSE_FLAG) {
		closedevice() ;
	}
	
	flush2device (0) ;
	
	return NULL;
}



int test1() {
	// Init the device and then close it:
	
	int init = initdevice (OUT1) ;
	if (init < 0) {
		printf ("[ERROR] Failed to initialize device.\n") ;
		return 1 ;
	}
	
	closedevice() ;
	
	// printf ("[DEBUG] CP 1\n") ;
	
	return 0 ;
}

int test2() {
	// Double init the device:
	
	int init = initdevice (OUT1) ;
	if (init < 0) {
		printf ("[ERROR] Failed to initialize device.\n") ;
		return 1 ;
	}
	closedevice();
	int init2 = initdevice (OUT1) ;
	if (init2 < 0) {
		printf ("[ERROR] Failed to re-initialize device.\n") ;
		return 1 ;
	}
	
	return 0 ;
}

int test3 (bool initialize , bool preClose) {
	// One thread writes to the device:
	
	if (initialize) {
		int init = initdevice (OUT1) ;
		if (init < 0) {
			printf ("[ERROR] Failed to initialize device.\n") ;
			return 1 ;
		}
	}
	
	if (preClose) {
		closedevice() ;
	}
	
	
	pthread_t single_thread ;
	NUMOPS = 1 ;  // single op
	pthread_create (&single_thread , NULL , &mywriter , 0) ;
	void * ignore ;
	pthread_join (single_thread , &ignore) ;
	
	closedevice() ;
	
	return 0 ;
}


int test5() {
	// A single thread performs a series of write and flush:
	
	int init = initdevice (OUT1) ;
	if (init < 0) {
		printf ("[ERROR] Failed to initialize device.\n") ;
		return 1 ;
	}
	
	NUMOPS = 10 ;
	FLUSH_FLAG = true ;
	
	pthread_t single_thread ;
	pthread_create (&single_thread , NULL , &mywriter , 0) ;
	void * ignore ;
	pthread_join (single_thread , &ignore) ;
	
	closedevice() ;
	
	FLUSH_FLAG = false ;
	
	return 0 ;
}


int test6() {
	// Close and then flush:
	
	int init = initdevice (OUT1) ;
	if (init < 0) {
		printf ("[ERROR] Failed to initialize device.\n") ;
		return 1 ;
	}
	
	NUMOPS = 1 ;
	CLOSE_FLAG = true ;
	
	pthread_t single_thread ;
	pthread_create (&single_thread , NULL , &myflusher , 0) ;
	void * ignore ;
	pthread_join (single_thread , &ignore) ;
	
	CLOSE_FLAG = false ;
	
	return 0 ;
}


int test7() {
	// Write to an uninitialized device:
	return test3 (false , false) ;
}


int test8() {
	// Illegal flush:
	
	int init = initdevice (OUT1) ;
	if (init < 0) {
		printf ("[ERROR] Failed to initialize device.\n") ;
		return 1 ;
	}
	
	NUMOPS = 1 ;
	FLUSH_FLAG = true ;
	ILLEGAL_FLUSH = true ;
	
	pthread_t single_thread ;
	pthread_create (&single_thread , NULL , &mywriter , 0) ;
	void * ignore ;
	pthread_join (single_thread , &ignore) ;  
	
	FLUSH_FLAG = false ;
	ILLEGAL_FLUSH = false ;
	
	closedevice() ;
	
	return 0 ;
}

int test9 (bool big_buf) {
	// Run the device with a number of threads:
	
	int init = initdevice (OUT2) ;
	if (init < 0) {
		printf ("[ERROR] Failed to initialize device.\n") ;
		return 1 ;
	}
	
	if (big_buf) {
		BIG_BUFFER = true ;
	}
	NUMOPS = 30 ;
	const int NUMTHREADS = 10 ;
	pthread_t producers[NUMTHREADS];
	
	for (int i = 0; i < NUMTHREADS; i++) {
		pthread_create(&producers[i], NULL, &mywriter, (void *)(i+1));
	}
	
	void * ignore ;
	for (int i = 0; i < NUMTHREADS; i++) {
		pthread_join(producers[i], &ignore);
	}
	
	closedevice();
	
	return(0);
}


int test10() {
	// Using very big buffers:
	return test9 (true) ;
}


int test11() {
	// init, close and then write
	return test3 (true , true) ;
}

int test12() {
	int init = initdevice (OUT1);
	if (init < 0) {
		printf ("[ERROR] Failed to initialize device.\n") ;
		return 1 ;
	}
	const int NUMTHREADS = 100 ;
	pthread_t producers[NUMTHREADS];

	for (int i = 0; i < NUMTHREADS; i++) {
		pthread_create(&producers[i], NULL, &mytimewriter, (void *)(i+1));
	}

	void * ignore ;
	for (int i = 0; i < NUMTHREADS; i++) {
		pthread_join(producers[i], &ignore);
	}

	closedevice();

	return 0;
}

int test13() {
	FLUSH_FLAG = true;
	int status = test12();
	FLUSH_FLAG = false;
	return status;
}
